﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.IO;
using OrderAggregator.DataStorage;
using OrderAggregator.InputFormats;


namespace OrderAggregator.Core
{
    /// <summary>
    /// Aggregator
    /// </summary>
    public class Aggregator
    {
        public delegate Storage.OrganizationRow OrganizationResolveHandler(Storage storage, BaseOrder order);

        public delegate Storage.ProductRow ProductResolveHandler(Storage.OrganizationRow organization, BaseOrderRow order);

        private readonly AggregatorConfig _config;

        private readonly BaseOrderFormat[] _formats;

        private readonly ExcelExporter _excelExporter;

        private readonly DataStorage.Storage _storage = new Storage();

        private int _processedOrders = 0;

        private readonly ProductResolveHandler _unknownProductResolver;
        private readonly OrganizationResolveHandler _unknownOrganizationResolver;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="config"></param>
        /// <param name="storage"></param>
        public Aggregator(
            AggregatorConfig config,
            DataStorage.Storage storage,
            ProductResolveHandler unknownProductResolver,
            OrganizationResolveHandler unknownOrganizationResolver
            )
        {
            _config = config;
            _storage = storage;
            _unknownProductResolver = unknownProductResolver;
            _unknownOrganizationResolver = unknownOrganizationResolver;

            _config.InputFolder = GetFullPath(_config.InputFolder);
            _config.OutputFolder = GetFullPath(_config.OutputFolder);


            _formats = BaseOrderFormat.AllFormats;

            _excelExporter = new ExcelExporter();

        }

        /// <summary>
        /// Кол-во обработанных заказов
        /// </summary>
        public int ProcessedOrders
        {
            get
            {
                return _processedOrders;
            }
        }

        /// <summary>
        /// Pre do
        /// </summary>
        public void PreDo()
        {
            var formulas = _storage.Formula
                .Where(
                disc =>
                !string.IsNullOrEmpty(disc.Expression)
                )
                .Select(disc => disc.Expression);

            Formulas.FormulaManager.Default.PreCompile(formulas);

        }

        /// <summary>
        /// Do
        /// </summary>
        public void Do()
        {
            var inputFiles = Directory.GetFiles(_config.InputFolder);

            if (inputFiles.Length == 0)
            {
                return;
            }

            foreach (var inputFile in inputFiles)
            {
                var order = ParseInput(inputFile, _formats);

                if (order == null)
                {
                    continue;
                }
                               
                
                DepartmetOrder[] departmetOrders = ProccesOrder(order);

                //обработка формулами

                if (departmetOrders != null)
                {
                    foreach (var departmetOrder in departmetOrders)
                    {
                        ProcessByFormula(departmetOrder);
                    }
                }


                //запись заявок

                string outFolder = DateTime.Now.ToShortDateString();
                outFolder = Path.Combine(_config.OutputFolder, outFolder);

                outFolder = Path.Combine(
                    outFolder,
                    string.Format("Заказ №{0} от {1}", order.Id, DateTime.Now.ToShortDateString())
                    );

                if (!Directory.Exists(outFolder))
                {
                    Directory.CreateDirectory(outFolder);
                }

                if (departmetOrders != null)
                {
                    foreach (var departmetOrder in departmetOrders)
                    {
                        var outFile = Path.Combine(outFolder, departmetOrder.Department.Name + ".xls");
                        _excelExporter.Export(outFile, departmetOrder);
                    }
                }

                System.IO.File.Copy(
                    inputFile,
                    Path.Combine(outFolder, Path.GetFileName(inputFile)),
                    true
                    );

                System.IO.File.Delete(inputFile);

                _processedOrders++;               
            }
        }

        private static void ProcessByFormula(DepartmetOrder departmetOrder)
        {
            var storage = (Storage)departmetOrder.Organization.Table.DataSet;

            foreach (var departmetOrderRow in departmetOrder.OrderRows)
            {
                var orderProduct = departmetOrderRow.Product;

                //поиск формул
                var discountRows = new List<Storage.DiscountRow>();
                var orgnztn = departmetOrder.Organization;
                while (orgnztn != null)
                {
                    discountRows.AddRange(
                        storage.Discount.Where(
                        d => (d.OrganizationRow == orgnztn && d.ProductRow == orderProduct) ||
                            (d.IsOrganizationIdNull() && d.ProductRow == orderProduct) ||
                            (d.OrganizationRow == orgnztn && d.IsProductIdNull()) ||
                            (d.IsOrganizationIdNull() && d.IsProductIdNull())
                            )
                            );

                    orgnztn = orgnztn.OrganizationRowParent;
                }
                               
                discountRows.Sort(CompareDiscounts);

                Storage.DiscountRow discountRow = discountRows.FirstOrDefault();


                if (discountRow != null &&
                    discountRow.FormulaRow != null
                    )
                {
                    var formulaFunc = Formulas.FormulaManager.Default.GetFormulaDelegate(discountRow.FormulaRow.Expression);

                    if (formulaFunc != null)
                    {
                        departmetOrderRow.Count = formulaFunc.Invoke(departmetOrderRow.Count);
                    }
                }

            }
        }

        /// <summary>
        /// Parse input
        /// </summary>
        /// <param name="inputFile"></param>
        /// <param name="formats"></param>
        /// <returns></returns>
        public static BaseOrder ParseInput(string inputFile, IEnumerable<BaseOrderFormat> formats)
        {
            FileStream stream = null;

            Func<Stream> getStream = () =>
            {
                if (stream != null && stream.CanSeek)
                {
                    stream.Seek(0, SeekOrigin.Begin);
                }
                else
                {
                    stream = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
                }

                return stream;
            };


            foreach (var format in formats)
            {
                try
                {
                    var order = format.ParseAndTranslate(inputFile, getStream);

                    if (order != null)
                    {
                        return order;
                    }
                }
                finally
                {
                    if (stream != null)
                    {
                        stream.Dispose();
                        stream = null;
                    }
                }
            }

            throw new Exception("Неизвестный формат заявки: " + inputFile);

        }

        private DepartmetOrder[] ProccesOrder(BaseOrder order)
        {
            var ordersByDepartment = new Dictionary<Storage.DepartmentRow, DepartmetOrder>();


            var organization = FindClientOrg(_storage, order);
            if (organization == null)
            {
                organization = _unknownOrganizationResolver.Invoke(_storage, order);
            }
            if (organization == null)
            {
                throw new AgregateException(
                    string.Format("В справочниках не найдена организация для '{0}'", order.Client)
                    );
            }

            foreach (var orderRow in order.OrderRows)
            {
                var product = FindProduct(organization, orderRow);

                if (product == null)
                {
                    product = _unknownProductResolver.Invoke(organization, orderRow);
                }

                if (product == null)
                {
                    throw new AgregateException(
                        string.Format("В справочниках не найден продукт указанный в заказе: {0}", orderRow.ProductName)
                        );
                }

                var department = product.DepartmentRow;

                if (department == null)
                {
                    throw new AgregateException(
                        string.Format("Цех для продукта не найден. Продукт: '{0}'", product.Name)
                        );
                }

                DepartmetOrder departmetOrder;
                if (!ordersByDepartment.TryGetValue(department, out departmetOrder))
                {
                    departmetOrder = new DepartmetOrder(department, organization);
                    departmetOrder.Date = order.ReceiveTime.Date;
                    ordersByDepartment[department] = departmetOrder;
                }

                var departmetOrderRow = departmetOrder.OrderRows.FirstOrDefault(depOrderRow => depOrderRow.Product == product);

                if (departmetOrderRow == null)
                {
                    departmetOrderRow = new DepartmetOrderRow(departmetOrder.OrderRows.Count + 1, product);
                    departmetOrder.OrderRows.Add(departmetOrderRow);
                }

                if (!string.IsNullOrEmpty(orderRow.UnitName))
                {
                    departmetOrderRow.UnitName = orderRow.UnitName;
                }
                else if (!product.IsUnitNameNull() && 
                    !string.IsNullOrEmpty(product.UnitName)
                    )
                {
                    departmetOrderRow.UnitName = product.UnitName;
                }

                departmetOrderRow.Count += orderRow.Count;
            }

            return ordersByDepartment.Values.ToArray();
        }

        private static Storage.OrganizationRow FindClientOrg(DataStorage.Storage storage, BaseOrder order)
        {
            return storage.Organization.FirstOrDefault(org => string.Equals(org.Name, order.Client));
        }

        private static Storage.ProductRow FindProduct(Storage.OrganizationRow organization, BaseOrderRow orderRow)
        {
            while (organization != null)
            {
                var relations = organization.GetOrganizationProductRelationRows();

                var productRef = relations.FirstOrDefault(
                    rel => string.Equals(rel.ProductCode, orderRow.ProductCode) ||
                        string.Equals(rel.ProductRow.Name, orderRow.ProductName)

                    );

                if (productRef != null)
                {
                    return productRef.ProductRow;
                }

                organization = organization.OrganizationRowParent;
            }

            return null;
        }

        private static string GetFullPath(string path)
        {
            if (Path.IsPathRooted(path))
            {
                return path;
            }

            return Path.Combine(
                Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location),
                path
                );
        }

        private static int CompareDiscounts(Storage.DiscountRow d1, Storage.DiscountRow d2)
        {
            if (CompareDiscountsAndGetLess(d1, d2))
            {
                return -1;
            }
            else if (CompareDiscountsAndGetLess(d2, d1))
            {
                return 1;
            }
            
            return 0;
        }

        private static bool CompareDiscountsAndGetLess(Storage.DiscountRow d1, Storage.DiscountRow d2)
        {
            if (!d1.IsProductIdNull() && d2.IsProductIdNull())
            {
                return true;
            }

            if (!d1.IsOrganizationIdNull())
            {
                if (d2.IsOrganizationIdNull())
                {
                    return true;
                }
                else if (d1.OrganizationRow.GetOrganizationOrder() > d2.OrganizationRow.GetOrganizationOrder())
                {
                    return true;
                }
            }

            return false;
        }
    }
}
